{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8) Using asynchronous functions with python 3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pyrpl uses the Qt eventloop to perform asynchronous tasks, but it has been set as the default loop of `asyncio`, such that you only need to learn how to use the standard python module [`asyncio`](https://docs.python.org/3/library/asyncio.html), and you don't need to know anything about Qt. To give you a quick overview of what can be done, we present in the following block an exemple of 2 tasks running in parrallele. The first one mimicks a temperature control loop, measuring periodically a signal every 1 s, and changing the offset of an `asg` based on the measured value (we realize this way a slow and rudimentary software pid). In parrallele, another task consists in repeatedly shifting the frequency of an asg, and measuring an averaged spectrum on the spectrum analyzer.\n",
    "\n",
    "Both tasks are defined by coroutines (a python function that is preceded by the keyword `async`, and that can contain the keyword `await`). Basically, the execution of each coroutine is interrupted whenever the keyword `await` is encountered, giving the chance to other tasks to be executed. It will only be resumed once the underlying coroutine's value becomes ready.\n",
    "\n",
    "Finally to execute the cocroutines, it is not enough to call `my_coroutine()`, since we need to send the task to the event loop. For that, we use the function `ensure_future` from pyrpl.async_utils module. This function immediately returns an object that is not the result of the task (not the object that is behind `return` inside the coroutine), but rather a Future object, that can be used to retrieve the actual result once it is ready (this is done by calling `future.result()` latter on).\n",
    "\n",
    "If you are executing the code inside the ipython notebook, then, this is all you have to do, since an event loop is already running in the back (a qt eventloop if you are using the option %pylab qt). Otherwise, you have to use one of the functions (`LOOP.run_forever()`, `LOOP.run_until_complete()`, or `LOOP.run_in_executor()`) to launch the eventloop."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#define-hostname\n",
    "HOSTNAME = '192.169.1.100'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from pyrpl import Pyrpl\n",
    "p = Pyrpl(config='tutorial', hostname=HOSTNAME)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "#no-test # Will be tested only on branch python3-only\n",
    "async def run_temperature_lock(setpoint=0.1):  # coroutines can receive arguments\n",
    "    with p.asgs.pop(\"temperature\") as asg: #  use the context manager \"with\" to \n",
    "    # make sure the asg will be freed after the acquisition\n",
    "        asg.setup(frequency=0, amplitue=0, offset=0) #  Use the asg as a dummy \n",
    "        while IS_TEMP_LOCK_ACTIVE: #  The loop will run untill this flag is manually changed to False\n",
    "                await asyncio.sleep(1) #  Give way to other coroutines for 1 s\n",
    "                measured_temp = asg.offset #  Dummy \"temperature\" measurment\n",
    "                asg.offset+= (setpoint - measured_temp)*0.1  #  feedback with an integral gain\n",
    "                print(\"measured temp: \", measured_temp) #  print the measured value to see how the execution flow works\n",
    "    \n",
    "async def run_n_fits(n): #  a coroutine to launch n acquisitions\n",
    "    sa = p.spectrumanalyzer\n",
    "    with p.asgs.pop(\"fit_spectra\") as asg: # use contextmanager again\n",
    "        asg.setup(output_direct='out1',\n",
    "                  trigger_source='immediately')\n",
    "        freqs = []  #  variables stay available all along the coroutine's execution\n",
    "        for i in range(n): #  The coroutine qill be executed several times on the await statement inside this loop\n",
    "            asg.setup(frequency=1000*i) #  Move the asg frequency\n",
    "            sa.setup(input=asg, avg=10, span=100e3, baseband=True) #  setup the sa for the acquisition\n",
    "            spectrum = await sa.single_async() #  wait for 10 averages to be ready\n",
    "            freq = sa.data_x[spectrum.argmax()] #  take the max of the spectrum\n",
    "            freqs.append(freq) #  append it to the result\n",
    "            print(\"measured peak frequency: \", freq) #  print to show how the execution goes\n",
    "        return freqs #  Once the execution is over, the Future will be filled with the result...\n",
    "\n",
    "from pyrpl.async_utils import ensure_future, sleep, wait\n",
    "IS_TEMP_LOCK_ACTIVE = True\n",
    "\n",
    "temp_future = ensure_future(run_temperature_lock(0.5)) # send temperature control task to the eventloop\n",
    "fits_future = ensure_future(run_n_fits(50)) # send spectrum measurement task to the eventloop \n",
    "sleep(5)\n",
    "IS_TEMP_LOCK_ACTIVE = False\n",
    "print(wait(fits_future))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
